package it.ap.sa.core.httpclient;

import java.io.IOException;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.BasicHttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StopWatch;

import it.ap.sa.core.utils.JsonParserUtil;
import it.ap.sa.core.utils.LogUtil;
import it.ap.sa.core.utils.costants.ContentTypeEnum;
import it.ap.sa.core.utils.costants.SecurityConstants;

/**
 * @author Alessandro Paolillo
 */
@SuppressWarnings("deprecation")
public abstract class AbstractHttpClient extends DefaultHttpClient{
	/**
	 * Mappa in cui salvare i cookie delle chiamate a SmartMoWeb per salvare i token di autenticazione
	 */
	private final Map<String,CookieBean> mapCookie = new HashMap<String, CookieBean>();

	/**
	 * logger
	 */
	protected final Logger logger = LoggerFactory.getLogger(this.getClass());
	
	public AbstractHttpClient() {
		super(new PoolingClientConnectionManager());
	}

	/**
	 * @author Alessandro Paolillo
	 */
	protected <T> T doJsonRestCall(String uri, Serializable input, Class<T> outputClass) throws Exception{
		return JsonParserUtil.jsonToObject(this.doJsonRestCall(uri ,input), outputClass);
	}
	/**
	 * @author Alessandro Paolillo
	 */
	protected <T> T doJsonRestCall(String uri, String jsonInput, Class<T> outputClass) throws Exception{
		return JsonParserUtil.jsonToObject(this.doJsonRestCall(uri
                                                              ,jsonInput
                                                              )
                                          ,outputClass
                                          );
	}
	/**
	 * @author Alessandro Paolillo
	 */
	protected String doJsonRestCall(String uri, Serializable input) throws Exception{
		return this.doRestCall(uri, JsonParserUtil.beanToJson(input), ContentTypeEnum.JSON);
	}
	/**
	 * @author Alessandro Paolillo
	 */
	protected String doJsonRestCall(String uri, String input) throws Exception{
		return this.doRestCall(uri, input, ContentTypeEnum.JSON);
	}
	/**
	 * @author Alessandro Paolillo
	 */
	protected String doXmlRestCall(String uri, String input) throws Exception{
		return this.doRestCall(uri, input, ContentTypeEnum.XML);
	}

	/**
	 * @author Alessandro Paolillo
	 */
	protected String doRestCall(String uri, String entityString, ContentTypeEnum contentType) throws Exception{
		StopWatch stopWatch = LogUtil.startStopWatch("doRestCall", logger);
		HttpPost  httpPost = null;
		try{
			logger.info("Uri da chiamare: "   + uri         );
			logger.info("String da inviare: " + entityString);
			logger.info("contentType: "       + contentType );
			httpPost  = new HttpPost(uri);
			if(contentType != null)
				httpPost.setHeader("Content-Type", contentType.getContentType());
			httpPost.setEntity(new StringEntity(entityString, "UTF-8"));


			return this.callRestService(httpPost);
		}catch(Exception e){
			logger.error("Error in doRestCall", e);
			throw e;
		}finally{
			LogUtil.endStopWatch(stopWatch, logger);
			if(httpPost != null){
				httpPost.releaseConnection();
			}
		}
	}
	/**
	 * @author Alessandro Paolillo
	 */
	protected <T> T doHttpJsonRestCall(String uri, Map<String,String> parameters, Class<T> outputClass) throws Exception{
		return JsonParserUtil.jsonToObject(this.doHttpJsonRestCall(uri
                                                                  ,parameters
                                                                  )
				                          ,outputClass
				                          );
	}
	/**
	 * @author Alessandro Paolillo
	 */
	protected <T> T doHttpJsonRestCall(String uri, Class<T> outputClass) throws Exception{
		return this.doHttpJsonRestCall(uri, null, outputClass);
	}
	/**
	 * @author Alessandro Paolillo
	 */
	protected String doHttpJsonRestCall(String uri) throws Exception{
		return this.doHttpJsonRestCall(uri, (Map<String,String>)null);
	}
	/**
	 * @author Alessandro Paolillo
	 */
	protected String doHttpJsonRestCall(String uri, Map<String,String> parameters) throws Exception{
		StopWatch stopWatch = LogUtil.startStopWatch("doHttpJsonRestCall", logger);
		try{
			logger.info("Uri da chiamare: " + uri);
			logger.info("parametri da inviatre: " + parameters);

			HttpPost            httpPost      = new HttpPost(uri);
			List<NameValuePair> urlParameters = new ArrayList<NameValuePair>();

			if(parameters != null){
				for(Entry<String,String> parameter : parameters.entrySet()){
					urlParameters.add(new BasicNameValuePair(parameter.getKey(), parameter.getValue()));
				}
				httpPost.setEntity(new UrlEncodedFormEntity(urlParameters));
			}

			return this.callRestService(httpPost);
		}catch(Exception e){
			logger.error("Error in doHttpJsonRestCall", e);
			throw e;
		}finally{
			LogUtil.endStopWatch(stopWatch, logger);
		}
	}
	/**
	 * @author Alessandro Paolillo
	 */
	private String callRestService(HttpPost httpPost) throws Exception{
		StopWatch stopWatch = LogUtil.startStopWatch("callRestService", logger);
		try{

			httpPost.setHeader("Connection", "close");

			HttpResponse response     = this.execute(httpPost);
			int          statusCode   = response.getStatusLine().getStatusCode();
			StringWriter stringWriter = new StringWriter();

			logger.info("Response code: " + statusCode);

			if(statusCode != HttpStatus.SC_OK)
				throw new Exception("Esito chiamata non corretto: " + statusCode);

			IOUtils.copy(response.getEntity().getContent(), stringWriter);

			String outputJson = stringWriter.toString();
			logger.info("Risposta: " + outputJson);

			return outputJson;
		}finally{
			LogUtil.endStopWatch(stopWatch, logger);
			if(httpPost != null){
				httpPost.releaseConnection();
			}
			this.closeIdleConnections();
		}
	}
	/**
	 * @author Alessandro Paolillo
	 */
	private void closeIdleConnections(){
		this.getConnectionManager().closeIdleConnections(0, TimeUnit.SECONDS);
	}
	/**
	 * @author Alessandro Paolillo
	 */
	@Override
	public CloseableHttpResponse execute(HttpUriRequest request) throws IOException, ClientProtocolException {
//Inizio check è SmartMoWeb		
		String uri      = request.getURI().toString();
		String path     = SecurityConstants.ACQUARIUM_WEB_PATH.value();
		int    index    = uri.indexOf(path);
		if(index    > 0){
//Se SmartMoWeb si deve inserrire token		
			String     host = uri.substring(0, index ) + (path);
			CookieBean bean = this.createOrGetSessionOnSmartMoWeb(host);
			try{
				request.addHeader(SecurityConstants.CSRF_HEADER_NAME.value(), bean.getXsrfToken());
				return super.execute(request, bean.getHttpContext());
			}finally{
				bean.aggiornaTimestamp();
			}
		}
		return super.execute(request);
	}
	/**
	 * @author Alessandro Paolillo
	 */
	private synchronized CookieBean createOrGetSessionOnSmartMoWeb(String host) throws ClientProtocolException, IOException{
		CookieBean bean = this.mapCookie.get(host);
		
		if(bean == null 
		|| bean.isValid() == false
		){
			BasicCookieStore cookieStore = new BasicCookieStore();
			BasicHttpContext httpContext = new BasicHttpContext();
			httpContext.setAttribute(HttpClientContext.COOKIE_STORE, cookieStore);

			super.execute(new HttpGet(host), httpContext);
			
			bean = new CookieBean(cookieStore, httpContext);
			this.mapCookie.put(host, bean);
		}
			
		return bean;
	}
}
